﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;


// source: http://wiki.unity3d.com/index.php/Interpolate#Interpolate.cs
namespace Yapp
{
    /**
     * Interpolation utility functions: easing, bezier, and catmull-rom.
     * Consider using Unity's Animation curve editor and AnimationCurve class
     * before scripting the desired behaviour using this utility.
     *
     * Interpolation functionality available at different levels of abstraction.
     * Low level access via individual easing functions (ex. EaseInOutCirc),
     * Bezier(), and CatmullRom(). High level access using sequence generators,
     * NewEase(), NewBezier(), and NewCatmullRom().
     *
     * Sequence generators are typically used as follows:
     *
     * IEnumerable<Vector3> sequence = Interpolate.New[Ease|Bezier|CatmulRom](configuration);
     * foreach (Vector3 newPoint in sequence) {
     *   transform.position = newPoint;
     *   yield return WaitForSeconds(1.0f);
     * }
     *
     * Or:
     *
     * IEnumerator<Vector3> sequence = Interpolate.New[Ease|Bezier|CatmulRom](configuration).GetEnumerator();
     * function Update() {
     *   if (sequence.MoveNext()) {
     *     transform.position = sequence.Current;
     *   }
     * }
     *
     * The low level functions work similarly to Unity's built in Lerp and it is
     * up to you to track and pass in elapsedTime and duration on every call. The
     * functions take this form (or the logical equivalent for Bezier() and CatmullRom()).
     *
     * transform.position = ease(start, distance, elapsedTime, duration);
     *
     * For convenience in configuration you can use the Ease(EaseType) function to
     * look up a concrete easing function:
     * 
     *  [SerializeField]
     *  Interpolate.EaseType easeType; // set using Unity's property inspector
     *  Interpolate.Function ease; // easing of a particular EaseType
     * function Awake() {
     *   ease = Interpolate.Ease(easeType);
     * }
     *
     * @author Fernando Zapata (fernando@cpudreams.com)
     * @Traduzione Andrea85cs (andrea85cs@dynematica.it)
     */

    public class Interpolate
    {


        /**
     * Different methods of easing interpolation.
     */
        public enum EaseType
        {
            Linear,
            EaseInQuad,
            EaseOutQuad,
            EaseInOutQuad,
            EaseInCubic,
            EaseOutCubic,
            EaseInOutCubic,
            EaseInQuart,
            EaseOutQuart,
            EaseInOutQuart,
            EaseInQuint,
            EaseOutQuint,
            EaseInOutQuint,
            EaseInSine,
            EaseOutSine,
            EaseInOutSine,
            EaseInExpo,
            EaseOutExpo,
            EaseInOutExpo,
            EaseInCirc,
            EaseOutCirc,
            EaseInOutCirc
        }

        /**
        * Sequence of eleapsedTimes until elapsedTime is >= duration.
        *
        * Note: elapsedTimes are calculated using the value of Time.deltatTime each
        * time a value is requested.
        */
        static Vector3 Identity(Vector3 v)
        {
            return v;
        }

        static Vector3 TransformDotPosition(Transform t)
        {
            return t.position;
        }


        static IEnumerable<float> NewTimer(float duration)
        {
            float elapsedTime = 0.0f;
            while (elapsedTime < duration)
            {
                yield return elapsedTime;
                elapsedTime += Time.deltaTime;
                // make sure last value is never skipped
                if (elapsedTime >= duration)
                {
                    yield return elapsedTime;
                }
            }
        }

        public delegate Vector3 ToVector3<T>(T v);
        public delegate float Function(float a, float b, float c, float d);

        /**
         * Generates sequence of integers from start to end (inclusive) one step
         * at a time.
         */
        static IEnumerable<float> NewCounter(int start, int end, int step)
        {
            for (int i = start; i <= end; i += step)
            {
                yield return i;
            }
        }

        /**
         * Returns sequence generator from start to end over duration using the
         * given easing function. The sequence is generated as it is accessed
         * using the Time.deltaTime to calculate the portion of duration that has
         * elapsed.
         */
        public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float duration)
        {
            IEnumerable<float> timer = Interpolate.NewTimer(duration);
            return NewEase(ease, start, end, duration, timer);
        }

        /**
         * Instead of easing based on time, generate n interpolated points (slices)
         * between the start and end positions.
         */
        public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, int slices)
        {
            IEnumerable<float> counter = Interpolate.NewCounter(0, slices + 1, 1);
            return NewEase(ease, start, end, slices + 1, counter);
        }



        /**
         * Generic easing sequence generator used to implement the time and
         * slice variants. Normally you would not use this function directly.
         */
        static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float total, IEnumerable<float> driver)
        {
            Vector3 distance = end - start;
            foreach (float i in driver)
            {
                yield return Ease(ease, start, distance, i, total);
            }
        }

        /**
         * Vector3 interpolation using given easing method. Easing is done independently
         * on all three vector axis.
         */
        static Vector3 Ease(Function ease, Vector3 start, Vector3 distance, float elapsedTime, float duration)
        {
            start.x = ease(start.x, distance.x, elapsedTime, duration);
            start.y = ease(start.y, distance.y, elapsedTime, duration);
            start.z = ease(start.z, distance.z, elapsedTime, duration);
            return start;
        }

        /**
         * Returns the static method that implements the given easing type for scalars.
         * Use this method to easily switch between easing interpolation types.
         *
         * All easing methods clamp elapsedTime so that it is always <= duration.
         *
         * var ease = Interpolate.Ease(EaseType.EaseInQuad);
         * i = ease(start, distance, elapsedTime, duration);
         */
        public static Function Ease(EaseType type)
        {
            // Source Flash easing functions:
            // http://gizma.com/easing/
            // http://www.robertpenner.com/easing/easing_demo.html
            //
            // Changed to use more friendly variable names, that follow my Lerp
            // conventions:
            // start = b (start value)
            // distance = c (change in value)
            // elapsedTime = t (current time)
            // duration = d (time duration)

            Function f = null;
            switch (type)
            {
                case EaseType.Linear: f = Interpolate.Linear; break;
                case EaseType.EaseInQuad: f = Interpolate.EaseInQuad; break;
                case EaseType.EaseOutQuad: f = Interpolate.EaseOutQuad; break;
                case EaseType.EaseInOutQuad: f = Interpolate.EaseInOutQuad; break;
                case EaseType.EaseInCubic: f = Interpolate.EaseInCubic; break;
                case EaseType.EaseOutCubic: f = Interpolate.EaseOutCubic; break;
                case EaseType.EaseInOutCubic: f = Interpolate.EaseInOutCubic; break;
                case EaseType.EaseInQuart: f = Interpolate.EaseInQuart; break;
                case EaseType.EaseOutQuart: f = Interpolate.EaseOutQuart; break;
                case EaseType.EaseInOutQuart: f = Interpolate.EaseInOutQuart; break;
                case EaseType.EaseInQuint: f = Interpolate.EaseInQuint; break;
                case EaseType.EaseOutQuint: f = Interpolate.EaseOutQuint; break;
                case EaseType.EaseInOutQuint: f = Interpolate.EaseInOutQuint; break;
                case EaseType.EaseInSine: f = Interpolate.EaseInSine; break;
                case EaseType.EaseOutSine: f = Interpolate.EaseOutSine; break;
                case EaseType.EaseInOutSine: f = Interpolate.EaseInOutSine; break;
                case EaseType.EaseInExpo: f = Interpolate.EaseInExpo; break;
                case EaseType.EaseOutExpo: f = Interpolate.EaseOutExpo; break;
                case EaseType.EaseInOutExpo: f = Interpolate.EaseInOutExpo; break;
                case EaseType.EaseInCirc: f = Interpolate.EaseInCirc; break;
                case EaseType.EaseOutCirc: f = Interpolate.EaseOutCirc; break;
                case EaseType.EaseInOutCirc: f = Interpolate.EaseInOutCirc; break;
            }
            return f;
        }

        /**
         * Returns sequence generator from the first node to the last node over
         * duration time using the points in-between the first and last node
         * as control points of a bezier curve used to generate the interpolated points
         * in the sequence. If there are no control points (ie. only two nodes, first
         * and last) then this behaves exactly the same as NewEase(). In other words
         * a zero-degree bezier spline curve is just the easing method. The sequence
         * is generated as it is accessed using the Time.deltaTime to calculate the
         * portion of duration that has elapsed.
         */
        public static IEnumerable<Vector3> NewBezier(Function ease, Transform[] nodes, float duration)
        {
            IEnumerable<float> timer = Interpolate.NewTimer(duration);
            return NewBezier<Transform>(ease, nodes, TransformDotPosition, duration, timer);
        }

        /**
         * Instead of interpolating based on time, generate n interpolated points
         * (slices) between the first and last node.
         */
        public static IEnumerable<Vector3> NewBezier(Function ease, Transform[] nodes, int slices)
        {
            IEnumerable<float> counter = NewCounter(0, slices + 1, 1);
            return NewBezier<Transform>(ease, nodes, TransformDotPosition, slices + 1, counter);
        }

        /**
         * A Vector3[] variation of the Transform[] NewBezier() function.
         * Same functionality but using Vector3s to define bezier curve.
         */
        public static IEnumerable<Vector3> NewBezier(Function ease, Vector3[] points, float duration)
        {
            IEnumerable<float> timer = NewTimer(duration);
            return NewBezier<Vector3>(ease, points, Identity, duration, timer);
        }

        /**
         * A Vector3[] variation of the Transform[] NewBezier() function.
         * Same functionality but using Vector3s to define bezier curve.
         */
        public static IEnumerable<Vector3> NewBezier(Function ease, Vector3[] points, int slices)
        {
            IEnumerable<float> counter = NewCounter(0, slices + 1, 1);
            return NewBezier<Vector3>(ease, points, Identity, slices + 1, counter);
        }

        /**
         * Generic bezier spline sequence generator used to implement the time and
         * slice variants. Normally you would not use this function directly.
         */
        static IEnumerable<Vector3> NewBezier<T>(Function ease, IList nodes, ToVector3<T> toVector3, float maxStep, IEnumerable<float> steps)
        {
            // need at least two nodes to spline between
            if (nodes.Count >= 2)
            {
                // copy nodes array since Bezier is destructive
                Vector3[] points = new Vector3[nodes.Count];

                foreach (float step in steps)
                {
                    // re-initialize copy before each destructive call to Bezier
                    for (int i = 0; i < nodes.Count; i++)
                    {
                        points[i] = toVector3((T)nodes[i]);
                    }
                    yield return Bezier(ease, points, step, maxStep);
                    // make sure last value is always generated
                }
            }
        }

        /**
         * A Vector3 n-degree bezier spline.
         *
         * WARNING: The points array is modified by Bezier. See NewBezier() for a
         * safe and user friendly alternative.
         *
         * You can pass zero control points, just the start and end points, for just
         * plain easing. In other words a zero-degree bezier spline curve is just the
         * easing method.
         *
         * @param points start point, n control points, end point
         */
        static Vector3 Bezier(Function ease, Vector3[] points, float elapsedTime, float duration)
        {
            // Reference: http://ibiblio.org/e-notes/Splines/Bezier.htm
            // Interpolate the n starting points to generate the next j = (n - 1) points,
            // then interpolate those n - 1 points to generate the next n - 2 points,
            // continue this until we have generated the last point (n - (n - 1)), j = 1.
            // We store the next set of output points in the same array as the
            // input points used to generate them. This works because we store the
            // result in the slot of the input point that is no longer used for this
            // iteration.
            for (int j = points.Length - 1; j > 0; j--)
            {
                for (int i = 0; i < j; i++)
                {
                    points[i].x = ease(points[i].x, points[i + 1].x - points[i].x, elapsedTime, duration);
                    points[i].y = ease(points[i].y, points[i + 1].y - points[i].y, elapsedTime, duration);
                    points[i].z = ease(points[i].z, points[i + 1].z - points[i].z, elapsedTime, duration);
                }
            }
            return points[0];
        }

        /**
         * Returns sequence generator from the first node, through each control point,
         * and to the last node. N points are generated between each node (slices)
         * using Catmull-Rom.
         */
        public static IEnumerable<Vector3> NewCatmullRom(Transform[] nodes, int slices, bool loop)
        {
            return NewCatmullRom<Transform>(nodes, TransformDotPosition, slices, loop);
        }

        /**
         * A Vector3[] variation of the Transform[] NewCatmullRom() function.
         * Same functionality but using Vector3s to define curve.
         */
        public static IEnumerable<Vector3> NewCatmullRom(Vector3[] points, int slices, bool loop)
        {
            return NewCatmullRom<Vector3>(points, Identity, slices, loop);
        }

        /**
         * Generic catmull-rom spline sequence generator used to implement the
         * Vector3[] and Transform[] variants. Normally you would not use this
         * function directly.
         */
        protected static IEnumerable<Vector3> NewCatmullRom<T>(IList nodes, ToVector3<T> toVector3, int slices, bool loop)
        {
            // need at least two nodes to spline between
            if (nodes.Count >= 2)
            {

                // yield the first point explicitly, if looping the first point
                // will be generated again in the step for loop when interpolating
                // from last point back to the first point
                yield return toVector3((T)nodes[0]);

                int last = nodes.Count - 1;
                for (int current = 0; loop || current < last; current++)
                {
                    // wrap around when looping
                    if (loop && current > last)
                    {
                        current = 0;
                    }
                    // handle edge cases for looping and non-looping scenarios
                    // when looping we wrap around, when not looping use start for previous
                    // and end for next when you at the ends of the nodes array
                    int previous = (current == 0) ? ((loop) ? last : current) : current - 1;
                    int start = current;
                    int end = (current == last) ? ((loop) ? 0 : current) : current + 1;
                    int next = (end == last) ? ((loop) ? 0 : end) : end + 1;

                    // adding one guarantees yielding at least the end point
                    int stepCount = slices + 1;
                    for (int step = 1; step <= stepCount; step++)
                    {
                        yield return CatmullRom(toVector3((T)nodes[previous]),
                                         toVector3((T)nodes[start]),
                                         toVector3((T)nodes[end]),
                                         toVector3((T)nodes[next]),
                                         step, stepCount);
                    }
                }
            }
        }

        /**
         * A Vector3 Catmull-Rom spline. Catmull-Rom splines are similar to bezier
         * splines but have the useful property that the generated curve will go
         * through each of the control points.
         *
         * NOTE: The NewCatmullRom() functions are an easier to use alternative to this
         * raw Catmull-Rom implementation.
         *
         * @param previous the point just before the start point or the start point
         *                 itself if no previous point is available
         * @param start generated when elapsedTime == 0
         * @param end generated when elapsedTime >= duration
         * @param next the point just after the end point or the end point itself if no
         *             next point is available
         */
        static Vector3 CatmullRom(Vector3 previous, Vector3 start, Vector3 end, Vector3 next,
                                    float elapsedTime, float duration)
        {
            // References used:
            // p.266 GemsV1
            //
            // tension is often set to 0.5 but you can use any reasonable value:
            // http://www.cs.cmu.edu/~462/projects/assn2/assn2/catmullRom.pdf
            //
            // bias and tension controls:
            // http://local.wasp.uwa.edu.au/~pbourke/miscellaneous/interpolation/

            float percentComplete = elapsedTime / duration;
            float percentCompleteSquared = percentComplete * percentComplete;
            float percentCompleteCubed = percentCompleteSquared * percentComplete;

            return previous * (-0.5f * percentCompleteCubed +
                                       percentCompleteSquared -
                                0.5f * percentComplete) +
                    start * (1.5f * percentCompleteCubed +
                               -2.5f * percentCompleteSquared + 1.0f) +
                    end * (-1.5f * percentCompleteCubed +
                                2.0f * percentCompleteSquared +
                                0.5f * percentComplete) +
                    next * (0.5f * percentCompleteCubed -
                                0.5f * percentCompleteSquared);
        }




        /**
         * Linear interpolation (same as Mathf.Lerp)
         */
        static float Linear(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime to be <= duration
            if (elapsedTime > duration) { elapsedTime = duration; }
            return distance * (elapsedTime / duration) + start;
        }

        /**
         * quadratic easing in - accelerating from zero velocity
         */
        static float EaseInQuad(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            return distance * elapsedTime * elapsedTime + start;
        }

        /**
         * quadratic easing out - decelerating to zero velocity
         */
        static float EaseOutQuad(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            return -distance * elapsedTime * (elapsedTime - 2) + start;
        }

        /**
         * quadratic easing in/out - acceleration until halfway, then deceleration
         */
        static float EaseInOutQuad(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
            if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime + start;
            elapsedTime--;
            return -distance / 2 * (elapsedTime * (elapsedTime - 2) - 1) + start;
        }

        /**
         * cubic easing in - accelerating from zero velocity
         */
        static float EaseInCubic(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            return distance * elapsedTime * elapsedTime * elapsedTime + start;
        }

        /**
         * cubic easing out - decelerating to zero velocity
         */
        static float EaseOutCubic(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            elapsedTime--;
            return distance * (elapsedTime * elapsedTime * elapsedTime + 1) + start;
        }

        /**
         * cubic easing in/out - acceleration until halfway, then deceleration
         */
        static float EaseInOutCubic(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
            if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime + start;
            elapsedTime -= 2;
            return distance / 2 * (elapsedTime * elapsedTime * elapsedTime + 2) + start;
        }

        /**
         * quartic easing in - accelerating from zero velocity
         */
        static float EaseInQuart(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            return distance * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
        }

        /**
         * quartic easing out - decelerating to zero velocity
         */
        static float EaseOutQuart(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            elapsedTime--;
            return -distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime - 1) + start;
        }

        /**
         * quartic easing in/out - acceleration until halfway, then deceleration
         */
        static float EaseInOutQuart(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
            if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
            elapsedTime -= 2;
            return -distance / 2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime - 2) + start;
        }


        /**
         * quintic easing in - accelerating from zero velocity
         */
        static float EaseInQuint(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            return distance * elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
        }

        /**
         * quintic easing out - decelerating to zero velocity
         */
        static float EaseOutQuint(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            elapsedTime--;
            return distance * (elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + 1) + start;
        }

        /**
         * quintic easing in/out - acceleration until halfway, then deceleration
         */
        static float EaseInOutQuint(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2f);
            if (elapsedTime < 1) return distance / 2 * elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + start;
            elapsedTime -= 2;
            return distance / 2 * (elapsedTime * elapsedTime * elapsedTime * elapsedTime * elapsedTime + 2) + start;
        }

        /**
         * sinusoidal easing in - accelerating from zero velocity
         */
        static float EaseInSine(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime to be <= duration
            if (elapsedTime > duration) { elapsedTime = duration; }
            return -distance * Mathf.Cos(elapsedTime / duration * (Mathf.PI / 2)) + distance + start;
        }

        /**
         * sinusoidal easing out - decelerating to zero velocity
         */
        static float EaseOutSine(float start, float distance, float elapsedTime, float duration)
        {
            if (elapsedTime > duration) { elapsedTime = duration; }
            return distance * Mathf.Sin(elapsedTime / duration * (Mathf.PI / 2)) + start;
        }

        /**
         * sinusoidal easing in/out - accelerating until halfway, then decelerating
         */
        static float EaseInOutSine(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime to be <= duration
            if (elapsedTime > duration) { elapsedTime = duration; }
            return -distance / 2 * (Mathf.Cos(Mathf.PI * elapsedTime / duration) - 1) + start;
        }

        /**
         * exponential easing in - accelerating from zero velocity
         */
        static float EaseInExpo(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime to be <= duration
            if (elapsedTime > duration) { elapsedTime = duration; }
            return distance * Mathf.Pow(2, 10 * (elapsedTime / duration - 1)) + start;
        }

        /**
         * exponential easing out - decelerating to zero velocity
         */
        static float EaseOutExpo(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime to be <= duration
            if (elapsedTime > duration) { elapsedTime = duration; }
            return distance * (-Mathf.Pow(2, -10 * elapsedTime / duration) + 1) + start;
        }

        /**
         * exponential easing in/out - accelerating until halfway, then decelerating
         */
        static float EaseInOutExpo(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
            if (elapsedTime < 1) return distance / 2 * Mathf.Pow(2, 10 * (elapsedTime - 1)) + start;
            elapsedTime--;
            return distance / 2 * (-Mathf.Pow(2, -10 * elapsedTime) + 2) + start;
        }

        /**
         * circular easing in - accelerating from zero velocity
         */
        static float EaseInCirc(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            return -distance * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) - 1) + start;
        }

        /**
         * circular easing out - decelerating to zero velocity
         */
        static float EaseOutCirc(float start, float distance, float elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 1.0f : elapsedTime / duration;
            elapsedTime--;
            return distance * Mathf.Sqrt(1 - elapsedTime * elapsedTime) + start;
        }

        /**
         * circular easing in/out - acceleration until halfway, then deceleration
         */
        static float EaseInOutCirc(float start, float distance, float
                             elapsedTime, float duration)
        {
            // clamp elapsedTime so that it cannot be greater than duration
            elapsedTime = (elapsedTime > duration) ? 2.0f : elapsedTime / (duration / 2);
            if (elapsedTime < 1) return -distance / 2 * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) - 1) + start;
            elapsedTime -= 2;
            return distance / 2 * (Mathf.Sqrt(1 - elapsedTime * elapsedTime) + 1) + start;
        }
    }
}